#include "IR.h"
#include <algorithm>
#include <cassert>
#include <iterator>
#include <map>
#include <memory>
#include <set>
#include <vector>

namespace sysy
{
    //===----------------------------------------------------------------------===//
    // Types
    //===----------------------------------------------------------------------===//

    Type *Type::getIntType(const int bits)
    {
        static auto i1 = IntType(1);
        static auto i8 = IntType(8);
        static auto i32 = IntType(32);
        switch (bits)
        {
        case 1:
            return &i1;
        case 8:
            return &i8;
        case 32:
            return &i32;
        default: assert(false);
        }
    }

    Type *Type::getFloatType()
    {
        static Type floatType(kFloat);
        return &floatType;
    }

    Type *Type::getVoidType()
    {
        static Type voidType(kVoid);
        return &voidType;
    }

    Type *Type::getLabelType()
    {
        static Type labelType(kLabel);
        return &labelType;
    }

    Type *Type::getPointerType(Type *baseType)
    {
        // forward to PointerType
        return PointerType::get(baseType);
    }

    Type *Type::getFunctionType(Type *returnType, const std::vector<Type *> &paramTypes)
    {
        // forward to FunctionType
        return FunctionType::get(returnType, paramTypes);
    }

    Type *Type::getArrayType(Type *baseType, const std::vector<int> &dims) { return ArrayType::get(baseType, dims); }

    int Type::getSize() const
    {
        switch (kind)
        {
        case kInt:
        case kFloat:
            return 4;
        case kLabel:
        case kPointer:
        case kFunction:
            return 8;
        case kVoid:
            return 0;
        }
        return 0;
    }

    ArrayType *ArrayType::get(Type *baseType, const std::vector<int> &dims)
    {
        std::stringstream ss;
        ss << dims[0];
        for (int i = 1; i < dims.size(); i++)
        {
            ss << "|" << dims[i];
        }
        auto dimsType = ss.str();
        static std::map<std::pair<const Type *, const std::string>, std::unique_ptr<ArrayType>> arrayTypes;
        auto iter = arrayTypes.find(std::pair<const Type *, const std::string>(baseType, dimsType));
        if (iter != arrayTypes.end())
            return iter->second.get();
        auto type = new ArrayType(baseType, dims);
        assert(type);
        auto res = arrayTypes.emplace(std::pair<const Type *, const std::string>(baseType, dimsType), type);
        return res.first->second.get();
        // auto type = new ArrayType(baseType, dims);
        // assert(type);
        // return type;
    }

    const std::string ArrayType::toString()
    {
        std::stringstream ss;
        ss << baseType->toString();
        std::string tystr = ss.str();
        for (auto i = dims.rbegin(); i != dims.rend(); ++i)
        {
            ss.str("");
            ss << "[ " << *i << " x " << tystr << " ]";
            tystr = ss.str();
        }
        return tystr;
    }

    PointerType *PointerType::get(Type *baseType)
    {
        static std::map<Type *, std::unique_ptr<PointerType>> pointerTypes;
        auto iter = pointerTypes.find(baseType);
        if (iter != pointerTypes.end())
            return iter->second.get();
        auto type = new PointerType(baseType);
        assert(type);
        auto result = pointerTypes.emplace(baseType, type);
        return result.first->second.get();
    }

    const std::string PointerType::toString() { return getBaseType()->toString() + "*"; }

    FunctionType *FunctionType::get(Type *returnType, const std::vector<Type *> &paramTypes)
    {
        static std::set<std::unique_ptr<FunctionType>> functionTypes;
        auto iter = std::find_if(
            functionTypes.begin(), functionTypes.end(),
            [&](const std::unique_ptr<FunctionType> &type) -> bool
            {
                if (returnType != type->getReturnType() or paramTypes.size() != type->getParamTypes().size())
                    return false;
                return std::equal(paramTypes.begin(), paramTypes.end(), type->getParamTypes().begin());
            });
        if (iter != functionTypes.end())
            return iter->get();
        auto type = new FunctionType(returnType, paramTypes);
        assert(type);
        auto result = functionTypes.emplace(type);
        return result.first->get();
    }

    void Value::replaceAllUsesWith(Value *value)
    {
        for (auto &use : uses)
            use->getUser()->setOperand(use->getIndex(), value);
        uses.clear();
    }

    ConstantValue *ConstantValue::get(int value, const std::string &name, int bits)
    {
        static std::map<std::pair<int, int>, std::unique_ptr<ConstantValue>> intConstants;
        auto iter = intConstants.find(std::pair<int, int>(value, bits));
        if (iter != intConstants.end())
            return iter->second.get();
        auto inst = new ConstantValue(value, name, bits);
        assert(inst);
        auto result = intConstants.emplace(std::pair<int, int>(value, bits), inst);
        return result.first->second.get();
    }

    ConstantValue *ConstantValue::get(float value, const std::string &name)
    {
        static std::map<float, std::unique_ptr<ConstantValue>> floatConstants;
        auto iter = floatConstants.find(value);
        if (iter != floatConstants.end())
            return iter->second.get();
        auto inst = new ConstantValue(value, name);
        assert(inst);
        auto result = floatConstants.emplace(value, inst);
        return result.first->second.get();
    }

    const std::string ConstantValue::toString()
    {
        if (isInt())
        {
            return std::to_string(getInt());
        }
        else
        {
            return "[float value]";
        }
    }

    void User::setOperand(int index, Value *value)
    {
        assert(index < getNumOperands());
        operands[index].setValue(value);
    }

    void User::replaceOperand(int index, Value *value)
    {
        assert(index < getNumOperands());
        auto &use = operands[index];
        use.getValue()->removeUse(&use);
        use.setValue(value);
    }

    CallInst::CallInst(Function *callee, const std::vector<Value *> args, BasicBlock *parent, const std::string &name) :
        Instruction(kCall, callee->getReturnType(), parent, name)
    {
        addOperand(callee);
        for (auto arg : args)
            addOperand(arg);
    }

    Function *CallInst::getCallee() { return dynamic_cast<Function *>(getOperand(0)); }

    const std::string CallInst::toString()
    {
        std::stringstream ss;
        auto func = getCallee();
        if (func->getReturnType()->isVoid())
        {
            ss << "call ";
        }
        else
            ss << name << " = call ";
        ss << func->getReturnType()->toString() << " @" << func->getName() << "(";
        if (!getArguments().empty())
        {
            ss << operands[1].getValue()->getType()->toString() << " " << operands[1].getValue()->getName();
            for (int i = 1; i < getArguments().size(); i++)
            {
                ss << ", " << operands[i + 1].getValue()->getType()->toString() << " "
                    << operands[i + 1].getValue()->getName();
            }
        }
        ss << ")";
        return ss.str();
    }

    const std::string LoadInst::toString()
    {
        std::stringstream ss;
        ss << name << " = load " << type->toString() << ", " << operands.back().getValue()->getType()->toString()
            << " ";
        //           << "%" << operands.back().getValue()->getName();
        if (dynamic_cast<GlobalValue *>(operands.back().getValue()))
            ss << "@";
        //    else
        //        ss << "%";
        ss << operands.back().getValue()->getName();
        return ss.str();
    }

    const std::string StoreInst::toString()
    {
        std::stringstream ss;
        ss << "store " << operands[0].getValue()->getType()->toString() << " " << operands[0].getValue()->getName()
            << ", " << operands[1].getValue()->getType()->toString() << " ";
        if (dynamic_cast<GlobalValue *>(operands[1].getValue()))
        {
            ss << "@";
        }
        ss << operands[1].getValue()->getName();
        return ss.str();
    }

    const std::string UnaryInst::toString()
    {
        std::stringstream ss;

        auto opr = operands[0].getValue();
        std::string oprName = opr->getName();
        // if (dynamic_cast<GlobalValue*>(opr)) {
        //     oprName = "@" + opr->getName();
        // }
        // else {
        //     oprName = "%" + opr->getName();
        // }

        ss << name << " = ";
        switch (kind)
        {
        case kNeg:
            ss << "sub i32 0, " << oprName;
            break;
        case kFNeg:
            ss << "fneg float " << oprName;
            break;
        case kNot:
        {
            if (opr->isBool())
                ss << "icmp eq i1 0, " << oprName;
            else
                ss << "icmp eq i32 0, " << oprName;
        }
        break;
        case kZext:
            ss << "zext i1 " << oprName << " to i32";
            break;
        case kIToF:
            ss << "sitofp i32 " << oprName << " to float";
            break;
        case kFtoI:
            ss << "fptosi float " << oprName << " to i32";
            break;
        default:
            std::cerr << "Unimplemented unary op" << std::endl;
            exit(-1);
        }
        return ss.str();
    }

    const std::string GEPInst::toString()
    {
        auto pointer = getOperand(0);
        std::stringstream ss;
        ss << name << " = " << "getelementptr ";
        auto gv = dynamic_cast<GlobalValue *>(pointer);
        auto av = dynamic_cast<AllocaInst *>(pointer);
        auto ld = dynamic_cast<LoadInst *>(pointer);
        assert(gv || av || ld);

        if (gv)
        {
            ss << gv->getArrayTypeStr() << ", " << gv->getArrayTypeStr() << "* @" << gv->getName();
            if (gv->getNumDims() != 0)
            {
                // 数组, 跳过外层索引
                // 指针无需此步
                ss << ", i32 0";
            }
        }
        else if (av)
        {
            ss << av->getArrayTypeStr() << ", " << av->getArrayTypeStr() << "* " << av->getName();
            if (av->getNumDims() != 0)
            {
                ss << ", i32 0";
            }
        }
        else
        {
            ss << ld->getType()->as<PointerType>()->getBaseType()->toString() << ", " << ld->getType()->toString()
                << " " << ld->getName();
        }

        for (int i = 1; i < operands.size(); i++)
        {
            ss << ", i32 " << getOperand(i)->getName();
        }

        return ss.str();
    }
} // namespace sysy
